////////////////////////////////////////////////////////////////////
// Programme de compression/décompression LZSS pour Chrono Cross
////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////
// Nemesis pour Terminus Traduction (tt_nemesis@yahoo.fr)
// Date de création :		09/09/03
// Dernière modification :	07/02/04
////////////////////////////////////////////////////////////////////

// Entete
#include "Lzss.h"

/*------------------------------------------------------------------
  Décompression LZSS d'un fichier
--------------------------------------------------------------------
 <- dir : dossier du fichier LZSS à décompresser
 <- fich : fichier LZSS à décompresser (sans extension pour dossier)
------------------------------------------------------------------*/
void Lzss_Decompression (char *dir, int fich)
{
	// Fichier Lzss à décompresser
	Bin_TFich *lzss_f;
	char lzss_nom[LZSS_NOM_MAX];
	char *lzss_nom2;
	// Fichier de sortie
	FILE *out_f;
	// Nom du dossier
	char dossier[LZSS_NOM_MAX];
	// Position d'écriture
	unsigned _int32 pos;
	// Infos du fichier Lzss
	unsigned _int32 taille,inconnu;
	// Informations de codage
	_int8 type,longueur,car;
	_int16 position;
	_int8 l;
	// Buffer de recherche
	char buffer[LZSS_BUFFER];

	// Recherche du fichier LZSS
	sprintf(lzss_nom,"%04d",fich);
	sprintf(dossier,"%s%s",DIR_FICH_IN,dir);
	Fichier_Cherche(dossier,lzss_nom,EXT_LZSS,&lzss_nom2);
	sprintf(lzss_nom,"%s%s",dossier,lzss_nom2);
	// Ouverture du fichier LZSS
	lzss_f=Bopen(lzss_nom,"rb");

	// Fichier existant ?
	if (lzss_f==NULL)
		Main_Message_S("Impossible d'ouvrir le fichier.\n","",ER);
	else {
		// Ouverture du fichier décompressé
		strcpy(&lzss_nom[strlen(lzss_nom)-3],EXT_SCENAR);
		out_f=fopen(lzss_nom,"wb");
		if (out_f==NULL)
			Main_Message_S("Impossible d'extraire.\n","",ER);
		else {
			// Lecture de l'entête
			inconnu=Bread_I(8*LZSS_ENTETE,lzss_f);
			// Taille décompressée
			taille=Bread_I(32,lzss_f);
			// Octets inconnus
			inconnu=Bread_M(8*LZSS_INCONNU,lzss_f);
			// Décompression du fichier
			pos=0;
			while (pos<taille) {
				// Type de codage
				type=(_int8)Bread_M(1,lzss_f);
				if (type==OCTET) {
					longueur=1;
					car=(_int8)Bread_M(8,lzss_f);
					buffer[pos%LZSS_BUFFER]=car;
					fputc(car,out_f);
				} else {
					position=(_int16)Bread_M(LZSS_BUFFER_B,lzss_f)-1;
					position%=LZSS_BUFFER;
					longueur=(_int8)Bread_M(LZSS_CHAINE_B,lzss_f)+LZSS_LG_MIN;
					for (l=0;l<longueur;l++){
						buffer[(pos+l)%LZSS_BUFFER]=buffer[position];
						fputc(buffer[position],out_f);
						position=(position+1)%LZSS_BUFFER;
					}
				}
				pos+=longueur;
				fflush(out_f);
			}
			// Fermeture du fichier extrait
			fclose(out_f);
		}
		// Fermeture du LZSS
		Bclose(lzss_f,OUT);
	}
}

/*------------------------------------------------------------------
  DéLZSSage d'un ensemble de fichiers
--------------------------------------------------------------------
 <- fich = ensemble des fichiers déterminés par des délimiteurs
	exemple : 1-4;5;8 = fichiers 1->5 + 8
------------------------------------------------------------------*/
void Lzss_D_Exec (char* fich) {
	// Numéros des fichiers à décompacter
	char* fichs_intervals[LZSS_FICHS_INTER];
	int interval,i;
	// Type d'intervalle
	int result;
	// Bornes
	unsigned _int16 fich_deb,fich_fin,r;
	// Dossier/fichiers
	char* dossier;
	char* fichiers;

	Main_Message_S("Decompression LZSS de %s... ",fich,OK);

	// Décomposition dossier/fichiers
	dossier=strtok(fich,SEPARATEUR);
	if (dossier[0]==NEUTRE)
		dossier[0]=0;
	fichiers=strtok(NULL,SEPARATEUR);
	
	// Décomposition des rooms en intervalles
	interval=0; // intervalle actuel
	fichs_intervals[interval]=strtok(fichiers,";");
	do
		interval++;
	while((fichs_intervals[interval]=strtok(NULL,";"))!=NULL);

	// Extension des intervalles
	for(i=0;i<interval;i++) {
		result=sscanf(fichs_intervals[i],"%d-%d",
			&fich_deb,&fich_fin);
		switch (result) {
		case 1:
			Lzss_Decompression(dossier,fich_deb);
			break;
		case 2:
			for (r=fich_deb;r<=fich_fin;r++)
				Lzss_Decompression(dossier,r);
			break;
		default:
			Main_Message_D("Erreur dans l'intervalle %d.\n",i+1,ER);
		}
	}
	Main_OK();
}

/*------------------------------------------------------------------
  Compression LZSS de fichiers
--------------------------------------------------------------------
 <- dir : dossier du fichier LZSS à compresser
 <- fich : fichier LZSS à décompresser (sans extension pour dossier)
------------------------------------------------------------------*/
void Lzss_Compression (char *dir, int fich)
{
	// Fichier d'entrée
	FILE *in_f;
	// Fichiers Lzss
	Bin_TFich *lzss_out_f,*lzss_in_f;
	char lzss_nom[LZSS_NOM_MAX];
	char *lzss_nom2;
	char lzss_nom_complet[LZSS_NOM_MAX];
	// Nom du dossier
	char dossier[LZSS_NOM_MAX];
	// Entete/taille du fichier LZSS décompressé
	unsigned _int32 taille,inconnu;
	// Position d'écriture
	unsigned _int32 pos_f;
	// Informations de codage
	unsigned _int8 longueur;
	unsigned _int16 position,p,pos;
	unsigned _int16 l;
	// Buffer de recherche
	unsigned char buffer[LZSS_BUFFER];
	/*unsigned char c;
	unsigned int buf_t;
	// Indice
	int i;*/

	sprintf(lzss_nom,"%04d",fich);
	// Ouverture du fichier à compresser
	sprintf(dossier,"%s%s",DIR_FICH_OUT,dir);
	Fichier_Cherche(dossier,lzss_nom,EXT_SCENAR,&lzss_nom2);
	sprintf(lzss_nom_complet,"%s%s",dossier,lzss_nom2);
	in_f=fopen(lzss_nom_complet,"rb");
	// Ouverture du fichier LZSS d'origine
	sprintf(dossier,"%s%s",DIR_FICH_IN,dir);
	Fichier_Cherche(dossier,lzss_nom,EXT_LZSS,&lzss_nom2);
	sprintf(lzss_nom_complet,"%s%s",dossier,lzss_nom2);
	lzss_in_f=Bopen(lzss_nom_complet,"rb");

	// Fichier existant ?
	if (lzss_in_f==NULL || in_f==NULL)
		Main_Message_S("Impossible d'ouvrir le fichier.\n","",ER);
	else {
		// Ouverture du fichier LZSS de sortie
		sprintf(dossier,"%s%s",DIR_FICH_OUT,dir);
		sprintf(lzss_nom_complet,"%s%s",dossier,lzss_nom2);
		lzss_out_f=Bopen(lzss_nom_complet,"wb");
		// Copie du début d'entête de l'original
		taille=Bread_M(32,lzss_in_f);
		Bwrite_M(taille,32,lzss_out_f);
		// Taille finale du fichier
		Bread_I(32,lzss_in_f);
		fseek(in_f,0,SEEK_END);
		taille=ftell(in_f);
		fseek(in_f,0,SEEK_SET);
		Bwrite_I(taille,32,lzss_out_f);
		// Copie de la portion inconnue
		inconnu=Bread_M(8*LZSS_INCONNU,lzss_in_f);
		Bwrite_M(inconnu,8*LZSS_INCONNU,lzss_out_f);
		/* Version réduite, on ne compresse même pas 
		// (ça ira d'autant plus vite à l'exécution)
		c=fgetc(in_f);
		while(!feof(in_f)) {
			Bwrite_M((_int32)OCTET,1,lzss_out_f);
			Bwrite_M((_int32)c,8,lzss_out_f);
			c=fgetc(in_f);
		}*/
		/* Version intégrale compressée 1 
		// Chargement du buffer
		while((buf_t=fread(buffer,sizeof(char),LZSS_BUFFER,in_f))!=0) {
			pos=0;
			// Parcours du buffer côté lecture
			while(pos<buf_t) {
				// Parcours du buffer côté recherche
				longueur=LZSS_LG_MIN-1;
				for(p=0;p<pos;p++) {
					// Recherche de chaine similaire maximale
					l=0;
					while(l<LZSS_CHAINE && p+l<pos && pos+l<buf_t
					  && buffer[p+l]==buffer[pos+l])
						l++;
					if (l>=longueur) {
						longueur=(_int8)l;
						position=p;
					}
				}
				// Cas d'une chaine à coder
				if (longueur>=LZSS_LG_MIN) {
					Bwrite_M((_int32)CODE,1,lzss_out_f);
					Bwrite_M((_int32)(position+1),12,lzss_out_f);
					Bwrite_M((_int32)(longueur-LZSS_LG_MIN),4,lzss_out_f);
				// Cas d'un caractère simple
				} else {
					longueur=1;
					Bwrite_M((_int32)OCTET,1,lzss_out_f);
					Bwrite_M((_int32)buffer[pos],8,lzss_out_f);
				};
				// On avance
				pos+=longueur;
			}
		}*/
		/* Version intégrale compressée 2 */
		// Parcours du fichier d'entrée
		buffer[0]=fgetc(in_f);
		do {
			pos_f = ftell(in_f);
			pos=(_int16)(pos_f-1)%LZSS_BUFFER;
			// Longueur de chaîne minimale à trouver
			longueur=0;
			// Recherche de chaine similaire maximale
			// avec chargement des caractères en buffer
			for(p=__max(0,((signed)pos_f-LZSS_BUFFER+LZSS_CHAINE));
				p<pos_f-1;
				p++)
			{
				l=0;
				while(l<LZSS_CHAINE && pos+l<LZSS_BUFFER && p%LZSS_BUFFER<LZSS_BUFFER-2
					&& buffer[(p+l)%LZSS_BUFFER]==buffer[(pos+l)%LZSS_BUFFER] && feof(in_f)==0) {
					l++;
					if (l>longueur)
						buffer[(pos+l)%LZSS_BUFFER]=fgetc(in_f);
					if (feof(in_f)!=0)
						Btell(lzss_out_f);
				}
				if (l>=longueur) {
					longueur=(_int8)l;
					position=p%LZSS_BUFFER;
				}
			}
			// Cas d'une chaine à coder
			if (longueur>=LZSS_LG_MIN) {
				Bwrite_M((_int32)CODE,1,lzss_out_f);
				Bwrite_M((_int32)(position+1),12,lzss_out_f);
				Bwrite_M((_int32)(longueur-LZSS_LG_MIN),4,lzss_out_f);
			// Cas d'un caractère simple
			} else {
				if (longueur==0)
					buffer[pos+1]=fgetc(in_f);
				longueur=1;
				Bwrite_M((_int32)OCTET,1,lzss_out_f);
				Bwrite_M((_int32)buffer[pos],8,lzss_out_f);
			};
		} while(feof(in_f)==0);

		// Fin du fichier
		Bacheve(lzss_out_f);
		Bwrite_M(0x0000,16,lzss_out_f);
		while(Btell(lzss_out_f)%8!=5)
			Bwrite_M(0x00,8,lzss_out_f);
		Bwrite_M(0x7777,16,lzss_out_f);
		// Fermeture des fichiers
		Bclose(lzss_out_f,IN);
		Bclose(lzss_in_f,OUT);
		fclose(in_f);
	}
}

/*------------------------------------------------------------------
  ReLZSSage d'un ensemble de fichiers
--------------------------------------------------------------------
 <- fich = ensemble des fichiers déterminés par des délimiteurs
	exemple : 1-4;5;8 = fichiers 1->5 + 8
------------------------------------------------------------------*/
void Lzss_C_Exec (char* fich) {
	// Numéros des fichiers à recompacter
	char* fichs_intervals[LZSS_FICHS_INTER];
	int interval,i;
	// Type d'intervalle
	int result;
	// Bornes
	unsigned _int16 fich_deb,fich_fin,r;
	// Dossier/fichiers
	char* dossier;
	char* fichiers;

	Main_Message_S("Compression LZSS de %s... ",fich,OK);

	// Décomposition dossier/fichiers
	dossier=strtok(fich,SEPARATEUR);
	if (dossier[0]==NEUTRE)
		dossier[0]=0;
	fichiers=strtok(NULL,SEPARATEUR);
	
	// Décomposition des rooms en intervalles
	interval=0; // intervalle actuel
	fichs_intervals[interval]=strtok(fichiers,";");
	do
		interval++;
	while((fichs_intervals[interval]=strtok(NULL,";"))!=NULL);

	// Extension des intervalles
	for(i=0;i<interval;i++) {
		result=sscanf(fichs_intervals[i],"%d-%d",
			&fich_deb,&fich_fin);
		switch (result) {
		case 1:
			Lzss_Compression(dossier,fich_deb);
			break;
		case 2:
			for (r=fich_deb;r<=fich_fin;r++)
				Lzss_Compression(dossier,r);
			break;
		default:
			Main_Message_D("Erreur dans l'intervalle %d.\n",i+1,ER);
		}
	}
	Main_OK();
}
